#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include <modbus/modbus.h> // 引用libmodbus库
#include "kc031service.h"
#include "mqtt.h"
#include "aexudp.h"
#include "aexgpio.h"
#include "mod_gpio.h"

typedef struct modbusConfigurition{
    unsigned char used_key;         //物理启动键与KC031连接的输入口序号
    unsigned char used;           //是否需要使用modbus
    unsigned int band;            //波特率
    unsigned char databit;        //数据位
    unsigned char stopbit;        //停止位

    unsigned char temp_in_devid;            //进口温度的RS485从站地址
    unsigned int temp_inaddr;               //进口温度的Modbus输入寄存器地址
    unsigned char temp_out_devid;           //出口温度的RS485从站地址
    unsigned int temp_outaddr;              //出口温度的Modbus输入寄存器地址
    unsigned char vr_out_devid;             //调压器输出从站地址
    unsigned int vr_outaddr;                //调压器输出保持寄存器地址
    unsigned int vr_outaddr2;               //调压器输出保持寄存器地址，第二个调压器的地址
    unsigned char w_devid;                  //功率读取设备的从站地址
    unsigned int w_startaddr;               //功率数据的起始寄存器地址
    unsigned char w_nreg;                   //功率数据的寄存器的数量
    unsigned int Heat_Capacity;             //比热容，1kg液体的比热容

    unsigned int maxW;                      //最大功率
    unsigned int ratedW;                    //限定功率
    unsigned int ratedFlow;                 //额定流量
    unsigned int ratedTemp;                 //设定的出口温度，单位为0.01℃（整数倍），启动功率可以是ratedFlow/20*(ratedTemp-inTempValue)，ratedTemp>inTempValue
    unsigned int mode;                      //加热模式

    unsigned char detaTemp;                 //温度的允许偏差，最终加热温度的允许误差。0.01℃的整数倍
    unsigned char heating_inv;              //加热器检测定时器时长，单位秒
}MODBUSCONFIG;

MODBUSCONFIG modbus_conf={
    .used = 0,
    .detaTemp = 50,
    .heating_inv = 0,
};

/**
 * 从ini配置取得配置信息放入响应的数据结构
 */
void modbus_config()
{
    psus_config *hcsc = get_psus_data();

    modbus_conf.used_key = StrToInt(hcsc->Heating_usedkey);
    modbus_conf.used = StrToInt(hcsc->Heating_used);
    modbus_conf.band = StrToInt(hcsc->Heating_baud);
    modbus_conf.databit = StrToInt(hcsc->Heating_data_bit);
    modbus_conf.stopbit = StrToInt(hcsc->Heating_stop_bit);

    modbus_conf.temp_in_devid = StrToInt(hcsc->Heating_temp_in_devid);
    modbus_conf.temp_inaddr = StrToInt(hcsc->Heating_temp_inaddr);
    modbus_conf.temp_out_devid = StrToInt(hcsc->Heating_temp_out_devid);
    modbus_conf.temp_outaddr = StrToInt(hcsc->Heating_temp_outaddr);
    modbus_conf.vr_out_devid = StrToInt(hcsc->Heating_vr_out_devid);
    modbus_conf.vr_outaddr = StrToInt(hcsc->Heating_vr_outaddr);
    modbus_conf.vr_outaddr2 = StrToInt(hcsc->Heating_vr_outaddr2);
    modbus_conf.w_devid = StrToInt(hcsc->Heating_w_devid);
    modbus_conf.w_startaddr = StrToInt(hcsc->Heating_w_startaddr);
    modbus_conf.w_nreg = StrToInt(hcsc->Heating_w_nreg);
    modbus_conf.Heat_Capacity = StrToInt(hcsc->Heating_Heat_Capacity);

    modbus_conf.maxW = StrToInt(hcsc->Heating_maxW);
    modbus_conf.ratedW = StrToInt(hcsc->Heating_ratedW);
    modbus_conf.ratedFlow = StrToInt(hcsc->Heating_ratedFlow);
    modbus_conf.ratedTemp = StrToInt(hcsc->Heating_ratedTemp);
    modbus_conf.detaTemp = StrToInt(hcsc->Heating_detaTemp);
    modbus_conf.mode = StrToInt(hcsc->Heating_mode);

    modbus_conf.heating_inv = StrToInt(hcsc->Heating_Heating_Inv);
}

void heating_mqtt_pub(unsigned int intemp,unsigned int outtemp,
    unsigned int curW,uint16_t *pw,int wnreg)
{
    char topic[100];
    char buf[200],wbuf[100],*pwbuf=wbuf;
    int len = 0,i=0;
    psus_config *hcsc = get_psus_data();
    time_t timer; 
    struct tm *tblock; 

    time(&timer); 
    tblock = localtime(&timer); 
    sprintf(pwbuf,"%d",pw[0]);
    for(i=0;i<wnreg;i++){
        pwbuf += strlen(pwbuf);
        sprintf(pwbuf,",%d",pw[i]);
    }
    snprintf(topic,sizeof(topic),"%1$s/%2$s/%2$s-%3$s/pub",getconf_string(hcsc->server_mqtttopic),
        hcsc->Heating_topic,getconf_string(hcsc->main_uuid));
    sprintf(buf,"{\"dt\":\"%.4d%.2d%.2d %.2d:%.2d:%.2d\",\"intemp\":\"%.2f\",\"outtemp\":\"%.2f\",\"curW\":\"%.3fkw\",\"pw\":[%s]}",
        tblock->tm_year+1900,
        tblock->tm_mon+1,
        tblock->tm_mday,
        tblock->tm_hour,
        tblock->tm_min,
        tblock->tm_sec,
        intemp/100.,outtemp/100.,curW/1000.,wbuf);
    mqtt_pub_with_topic(topic,buf, strlen(buf)+1);
}

unsigned long get_heating_inv()
{
    return modbus_conf.heating_inv > 0 ? modbus_conf.heating_inv : HCSRV_HEART_DELAY;
}

int get_heating_used()
{
    return modbus_conf.used && aexgpio_get_in_by_index(modbus_conf.used_key);
}

uint16_t temp_inv = 105;                        // 入口温度
uint16_t temp_outv = 605;                       // 出口温度
uint16_t w_data[10] = {0,0,0,0,0,0,0,0,0};      // 功率参数最多获取10个
uint16_t curW = 0;                             // 当前需要使用的加热功率

/**
 * 根据AI值计算温度值
 */
double calc_temp(uint16_t temp)
{
    double t=0;

    t = ((temp/249.)-4.)*200/16.-50;

    return t;
}

/**
 * 根据温度差计算需要功率，焦耳=mc*detaT，1焦耳=3600瓦时
 */
uint16_t calac_detatemp_w(uint16_t detaTemp)
{
    uint16_t wu32 = ((modbus_conf.ratedFlow/24.)*(modbus_conf.Heat_Capacity)*(detaTemp/100.))/3600;
    // unsigned long wu32 = ((modbus_conf.ratedFlow/240.)*(modbus_conf.Heat_Capacity)*detaTemp)*3.6;
    // psus_hint("  %s : %d %d",__func__,detaTemp,wu32);
    return wu32;
}

/**
 * 根据功率瓦特计算AO需要的数值，功率值为0~限定功率值，传入功率使用瓦特
 * 调压器需要直接给出电流值4000uA~20000uA
 */
uint16_t calc_curw(uint16_t w)
{
    double da = 16000.0/modbus_conf.maxW;           //计算出每uA对应的功率数值
    uint16_t dc = 4000;                            //功率为0的电流值，4mA
    
    if(w > modbus_conf.ratedW) curW = w = modbus_conf.ratedW;      //如果需要设置的功率超过限定功率值，则设置为限定功率值
    
    dc = 4000 + w*da;                                     //计算出所需要的uA值
    // psus_hint("  %s: w=%d,curW=%d,dc=%d,da=%.3f",__func__,w,curW,dc,da);
    return dc;
}

void hcsrv_buid_status_packnet(unsigned char cmd,unsigned char *data,int len)
{
    char buf[255];

	PPKTPROPO handle = aexudp_get_default_prkpropo();
	if(buf != NULL && handle != NULL){
		unsigned char index=0;
		unsigned char *pdata = AEXUDP_DATA_GET_PAYLOAD(AEXUDP_GET_DATA(buf));

		AEXUDP_SET_BEGIN(buf,handle->start);
		AEXUDP_SET_INDEX(buf,aexudp_get_packnet_index());
		AEXUDP_SET_CMD(buf,cmd);
		AEXUDP_DATA_SET_SRC(AEXUDP_GET_DATA(buf),aexgpio_get_mod_addr());
		AEXUDP_DATA_SET_DST(AEXUDP_GET_DATA(buf),0);
        AEXUDP_DATA_SET_PAYLOAD(AEXUDP_GET_DATA(buf),data,len);
		AEXUDP_SET_LEN(buf,len+2);
		if(handle->encrypt){
			handle->encrypt(buf,AEXUDP_GET_LEN(buf));
		}else{
			AEXUDP_SET_CRC(buf);
		}
		AEXUDP_SET_END(buf,handle->end);
        SendtoServer(buf,AEXUDP_GET_PACKET_LEN(buf));
	}else{
		memset(buf,0,len);
	}
}

// 自定义RTS控制函数
void custom_rs485_rts(modbus_t *ctx, int on)
{
#ifdef SGWEC_BOARD
#else
    modgpio_setrs485out(on);
#endif
}

int modbusmain()
{
    int ret,needmqtt=0;
    modbus_t *ctx = NULL;
    psus_config *hcsc = get_psus_data();
    uint16_t value=0;

    char *port = hcsc->Heating_port;

    modbus_config();
    if(!get_heating_used()){
        if(curW != 0){
            curW = 0;
            modbus_stop_vrv();
        }
        if(!modbus_conf.used){
            psus_hint("Heating is not used!");
        }
        return 0;
    }
    // 创建一个RTU类型的容器
    ctx = modbus_new_rtu(port, modbus_conf.band, hcsc->Heating_parity[0], modbus_conf.databit,modbus_conf.stopbit);

	modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    // 设置自定义RTS控制函数
    modbus_rtu_set_custom_rts(ctx, custom_rs485_rts);
	modbus_rtu_set_rts(ctx,MODBUS_RTU_RTS_UP);
    // 设置从端地址
    modbus_set_slave(ctx, modbus_conf.temp_in_devid);
    // 设置debug模式
    modbus_set_debug(ctx, true);
    // RTU 模式下表示打开串口
    if (modbus_connect(ctx) == -1) {
        psus_error("Connection failed: %s ", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    psus_hint("Modbus Connection successed");

    //读取流量
    // ret = modbus_read_input_registers(ctx,0,1,&value);
    // if(ret != 1){
    //     psus_hint("ERROR modbus_read_input_register (%d)", ret);
    // }else{
    //     //获取到数值，保存到变量
    //     psus_hint("  0x%X=%d",0,value);
    // }

    //读取进口温度
    modbus_set_slave(ctx, modbus_conf.temp_in_devid);
    ret = modbus_read_input_registers(ctx,modbus_conf.temp_inaddr,1,&value);
    if(ret != 1){
        psus_error("ERROR in port temp (%d)", ret);
        modbus_close(ctx);
        modbus_free(ctx);
        return 0;
    }else{
        //获取到数值，保存到变量
        temp_inv = calc_temp(value)*100;
        psus_hint(" IN-Temp:0x%X=%.2f℃",modbus_conf.temp_inaddr,temp_inv/100.);
    }

    //读取出口温度
    modbus_set_slave(ctx, modbus_conf.temp_out_devid);
    ret = modbus_read_input_registers(ctx,modbus_conf.temp_outaddr,1,&value);
    if(ret != 1){
        psus_hint("ERROR Out temp (%d)", ret);
        modbus_close(ctx);
        modbus_free(ctx);
        return 0;
    }else{
        //获取到数值，保存到变量
        temp_outv = calc_temp(value)*100;
        psus_hint(" OUT-Temp:0x%X=%.2f℃",modbus_conf.temp_outaddr,temp_outv/100.);
    }
    psus_hint("当前功率:%.3fkw",curW/1000.);
    if(get_heating_used()){
        if(abs(temp_outv-modbus_conf.detaTemp) < modbus_conf.detaTemp){
            //出口温度与设定温度差值在允许范围内，则说明加热功率与流量和温升匹配，不需要更改
        }else{
            uint16_t dv = calac_detatemp_w(abs(temp_outv-modbus_conf.ratedTemp));
            if(temp_outv > modbus_conf.ratedTemp){
                //温度高于设定温度，需要减少功率
                if(curW > dv)
                    curW -= dv;         //当前功率大于温差所需功率，减少功率
                else
                    curW = 0;           //当前功率小于等于温差所需功率，设置功率为 0
            }else{
                //温度低于设定温度，需要增加功率
                curW += dv;
            }
            needmqtt = 1;
        }
    }else{
        curW = 0;       //没有启用加热，所以功率设置为 0
    }
    psus_hint("设置功率:%.3fkw",curW/1000.);
    if(curW > modbus_conf.ratedW || curW > modbus_conf.maxW) 
        curW = (modbus_conf.maxW>modbus_conf.ratedW) ? modbus_conf.ratedW : modbus_conf.maxW;      //如果需要设定的功率大于限定的功率，则设置未限定功率,取最大功率和限制功率的最小值
    psus_hint("调整功率:%.3fkw",curW/1000.);

    // if(temp_outv > modbus_conf.ratedTemp){
    //     //出口温度大于设置温度，说明温度已经满足要求
    //     if(abs(temp_outv - temp_inv) > modbus_conf.detaTemp){
    //         if(modbus_conf.ratedTemp <= (temp_inv + modbus_conf.detaTemp)){
    //             //设定温度允许误差范围内不高于进口温度，不需要加热
    //             //关闭加热
    //             curW = 0;
    //             psus_hint("设定温度允许误差范围内不高于进口温度，不需要加热,功率设定为:%.3fkw",curW/1000.);
    //         }else{
    //             //出口温度超过设置温度并超过了允许范围，需要降低功率
    //             uint16_t dv = calac_detatemp_w(temp_outv-modbus_conf.ratedTemp);
    //             if(curW >= dv)
    //                 curW -= dv;
    //             psus_hint("出口温度超过设置温度并超过了允许范围，需要降低功率至：%.3fkw",curW/1000.);
    //         }
    //     }else{
    //         psus_hint("继续恒温：%0.3fkw",curW/1000.);
    //     }
    // }else if((modbus_conf.ratedTemp - temp_outv) > modbus_conf.detaTemp){
    //     //出口温度小于设置温度并且超过了允许的误差范围，增加加热功率
    //     curW += calac_detatemp_w(modbus_conf.ratedTemp-temp_outv);
    //     psus_hint("出口温度小于设置温度并且超过了允许的范围，增加加热功率至：%.3fkw",curW/1000.);
    // }

    //设定输出功率值
    {
        uint16_t vrv = calc_curw(curW);
        modbus_set_slave(ctx, modbus_conf.vr_out_devid);
        psus_hint("V modbus devid=%d.",modbus_conf.vr_out_devid);
        ret = modbus_write_register(ctx,modbus_conf.vr_outaddr,vrv);
        if(ret == -1){
            psus_hint("ERROR write output voltage (%s),reg=%04X=%d",modbus_strerror(errno),modbus_conf.vr_outaddr,vrv);
        }else{
            psus_hint("Set ouput power:%0.3fmA %0.3fkw",vrv/1000.,curW/2000.);
        }
        ret = modbus_write_register(ctx,modbus_conf.vr_outaddr2,vrv);
        if(ret == -1){
            psus_hint("ERROR write output voltage (%s) reg=%04X=%d", modbus_strerror(errno),modbus_conf.vr_outaddr2,vrv);
        }else{
            psus_hint("Set ouput power 2 :%0.3fmA %0.3fkw",vrv/1000.,curW/2000.);
        }
    }
    
    // //读取功率信息
    // modbus_set_slave(ctx, modbus_conf.w_devid);
    // psus_hint("V modbus devid=%d.",modbus_conf.w_devid);
    // ret = modbus_read_input_registers(ctx,modbus_conf.w_startaddr,modbus_conf.w_nreg,w_data);
    // if(ret == -1){
    //     psus_hint("Read power param error (%s),devid=%d,reg=%04X+%d", modbus_strerror(ret),modbus_conf.w_devid,modbus_conf.w_startaddr,modbus_conf.w_nreg);
    // }else{
    //     //获取到的寄存器数值，保存到变量
    //     int i = 0;
    //     for(i=0;i<modbus_conf.w_nreg;i++){
    //         psus_hint(" W-DATA[%d]:%d",modbus_conf.w_startaddr+i,w_data[i]);
    //     }
    // }
    // close the connection
    modbus_close(ctx);
    modbus_free(ctx);
    if(0){
        int index=0,i=0;
        char data[100];

        modbus_pwr(modbus_conf.w_startaddr,modbus_conf.w_nreg);
        data[index++] = (temp_inv>>8)&0xFF;
        data[index++] = temp_inv&0xFF;
        data[index++] = (temp_outv>>8)&0xFF;
        data[index++] = temp_outv&0xFF;
        data[index++] = (curW>>8)&0xFF;
        data[index++] = curW&0xFF;
        for(i=0;i<modbus_conf.w_nreg;i++){
            data[index++] = (w_data[i]>>8)&0xFF;
            data[index++] = w_data[i]&0xFF;
        }
        hcsrv_buid_status_packnet(AEX_CMD_AINEVENT,data,index);
    }
    if(needmqtt)
    {
        heating_mqtt_pub(temp_inv,temp_outv,curW,w_data,modbus_conf.w_nreg);
    }
    // psus_hint("modbusmain");
    return 1;
}

int modbus_pwr(int addr,int nreg)
{
    int ret;
    modbus_t *ctx = NULL;
    psus_config *hcsc = get_psus_data();
    uint16_t value=0;

    char *port = hcsc->Heating_port;

    modbus_config();
    // 创建一个RTU类型的容器
    ctx = modbus_new_rtu(port, modbus_conf.band, hcsc->Heating_parity[0], modbus_conf.databit,modbus_conf.stopbit);

	modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    // 设置自定义RTS控制函数
    modbus_rtu_set_custom_rts(ctx, custom_rs485_rts);
	modbus_rtu_set_rts(ctx,MODBUS_RTU_RTS_UP);
    //读取功率信息
    modbus_set_slave(ctx, modbus_conf.w_devid);
    // 设置debug模式
    modbus_set_debug(ctx, true);
    // RTU 模式下表示打开串口
    if (modbus_connect(ctx) == -1) {
        psus_error("Connection failed: %s ", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    nreg = nreg>10?10:nreg;
    nreg = nreg<1?1:nreg;
    modbus_conf.w_nreg = nreg;
    ret = modbus_read_registers(ctx,modbus_conf.w_startaddr,nreg,w_data);
    if(ret == -1){
        psus_hint("Read power param error (%s),devid=%d,reg=%04X+%d", modbus_strerror(errno),modbus_conf.w_devid,modbus_conf.w_startaddr,nreg);
        w_data[0] = 0;
    }else{
        //获取到的寄存器数值，保存到变量
        int i = 0;
        for(i=0;i<nreg;i++){
            psus_hint(" W-DATA[%d]:%d",modbus_conf.w_startaddr+i,w_data[i]);
        }
    }
    // close the connection
    modbus_close(ctx);
    modbus_free(ctx);
    // psus_hint("modbusmain");
    return w_data[0];
}

int modbus_stop_vrv()
{
    int ret;
    modbus_t *ctx = NULL;
    psus_config *hcsc = get_psus_data();
    uint16_t value=0;

    char *port = hcsc->Heating_port;

    modbus_config();
    // 创建一个RTU类型的容器
    ctx = modbus_new_rtu(port, modbus_conf.band, hcsc->Heating_parity[0], modbus_conf.databit,modbus_conf.stopbit);

	modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    // 设置自定义RTS控制函数
    modbus_rtu_set_custom_rts(ctx, custom_rs485_rts);
	modbus_rtu_set_rts(ctx,MODBUS_RTU_RTS_UP);
    modbus_set_slave(ctx, modbus_conf.vr_out_devid);
    // 设置debug模式
    modbus_set_debug(ctx, true);
    // RTU 模式下表示打开串口
    if (modbus_connect(ctx) == -1) {
        psus_error("Connection failed: %s ", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    curW = 0;
    {
        uint16_t vrv = calc_curw(curW);
        modbus_set_slave(ctx, modbus_conf.vr_out_devid);
        psus_hint("V modbus devid=%d.",modbus_conf.vr_out_devid);
        ret = modbus_write_register(ctx,modbus_conf.vr_outaddr,vrv);
        if(ret == -1){
            psus_hint("ERROR write output voltage (%s),reg=%04X=%d",modbus_strerror(errno),modbus_conf.vr_outaddr,vrv);
        }else{
            psus_hint("Set ouput power:%0.3fmA %0.3fkw",vrv/1000.,curW/2000.);
        }
        ret = modbus_write_register(ctx,modbus_conf.vr_outaddr2,vrv);
        if(ret == -1){
            psus_hint("ERROR write output voltage (%d) reg=%04X=%d", ret,modbus_conf.vr_outaddr2,vrv);
        }else{
            psus_hint("Set ouput power 2 :%0.3fmA %0.3fkw",vrv/1000.,curW/2000.);
        }
    }

    // close the connection
    modbus_close(ctx);
    modbus_free(ctx);
    // psus_hint("modbusmain");
    heating_mqtt_pub(temp_inv,temp_outv,curW,w_data,modbus_conf.w_nreg);
    return 0;
}
